package com.junior.fixclub.party;

import android.location.Location;
import android.os.Bundle;
import android.os.SystemClock;
import android.telephony.CellIdentityGsm;
import android.telephony.CellIdentityLte;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;

import com.junior.fixclub.party.location.VirtualGPSSatalines;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class FixLocation {

    public static String TAG = "FixClub";
    public void Fix(XC_LoadPackage.LoadPackageParam loadPackageParam){

    }

    public static void hookAndChange(XC_LoadPackage.LoadPackageParam loadPackageParam, final double latitude, final double longitude, final int lac, final int cid) {


        hookMethod("android.telephony.TelephonyManager", loadPackageParam.classLoader, "getNeighboringCellInfo", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param)
                    throws Throwable {
                param.setResult(null);
            }
        });

        hookMethods("android.location.LocationManager", "requestLocationUpdates", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                //location request start();
            }
        });

        hookMethod("android.location.LocationManager", loadPackageParam.classLoader, "getLastLocation", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Location location = (Location) param.getResult();
                if (location != null) {
                    updateLocation(location, latitude, longitude);
                    param.setResult(location);
                } else {
                    param.setResult(createLocation(latitude, longitude));
                }
            }
        });

        hookMethods("android.location.LocationManager", "getLastKnownLocation", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Location location = (Location) param.getResult();
                if (location != null) {
                    updateLocation(location, latitude, longitude);
                    param.setResult(location);
                } else {
                    param.setResult(createLocation(latitude, longitude));
                }
            }
        });

        hookMethod("android.location.Location", loadPackageParam.classLoader, "getLatitude", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(latitude);
            }
        });

        hookMethod("android.location.Location", loadPackageParam.classLoader, "getLongitude", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(longitude);
            }
        });

        hookMethods("android.location.LocationManager", "getBestProvider", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult("gps");
            }
        });

        hookMethods("android.location.LocationManager", "isProviderEnabled", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if ("gps".equals(param.args[0])) {
                    param.setResult(true);
                }
            }
        });

        hookMethod(GsmCellLocation.class, "getLac", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(lac);
            }
        });
        hookMethod(GsmCellLocation.class, "getCid", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(cid);
            }
        });

        hookMethod(CellIdentityGsm.class, "getLac", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(lac);
            }
        });

        hookMethod(CellIdentityGsm.class, "getCid", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(cid);
            }
        });

        hookMethod(CellIdentityLte.class, "getTac", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(lac);
            }
        });

        hookMethod(CellIdentityLte.class, "getCi", new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                param.setResult(cid);
            }
        });

    }

    //不带参数的方法拦截
    private static void hookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
        try {
            XposedHelpers.findAndHookMethod(clazz, methodName, parameterTypesAndCallback);
        } catch (Throwable e) {
            Log.e(TAG, e.toString());
        }
    }

    //不带参数的方法拦截
    private static void hookMethod(String className, ClassLoader classLoader, String methodName,Object... parameterTypesAndCallback) {
        try {
            XposedHelpers.findAndHookMethod(className, classLoader, methodName, parameterTypesAndCallback);
        } catch (Throwable e) {
            Log.e(TAG, e.toString());
        }
    }

    //带参数的方法拦截
    private static void hookMethods(String className, String methodName, XC_MethodHook xmh) {
        try {
            Class<?> clazz = Class.forName(className);
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.getName().equals(methodName) && !Modifier.isAbstract(method.getModifiers()) && Modifier.isPublic(method.getModifiers())) {
                    XposedBridge.hookMethod(method, xmh);
                }
            }
        } catch (Throwable e) {
            Log.e(TAG, e.toString());
        }
    }

    public static Location createLocation(double latitude, double longitude) {
        Location l = new Location(android.location.LocationManager.GPS_PROVIDER);
        l.setLatitude(latitude);
        l.setLongitude(longitude);
        l.setAccuracy(8f);
        l.setBearing((int) (360 * Math.random()));
        l.setTime(System.currentTimeMillis());
        l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
        Bundle extraBundle = new Bundle();
        int svCount = VirtualGPSSatalines.get().getSvCount();
        extraBundle.putInt("satellites", svCount);
        extraBundle.putInt("satellitesvalue", svCount);
        l.setExtras(extraBundle);
        return l;
    }

    public static void updateLocation(Location location, double latitude, double longitude) {
        location.setLatitude(latitude);
        location.setLongitude(longitude);
        location.setTime(System.currentTimeMillis());
        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
    }
}
